package com.gitee.zycra.union.api.common.util;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;

/**
 * @author zycra
 */
@Slf4j
public final class CaptchaUtil {

    /**
     * 随机字符数组,去掉1,0,i,o几个容易混淆的字符
     */
    private static final char[] CAPTCHA_CODES = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V'
            , 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9'};

    private static final Random RANDOM = new SecureRandom();

    private static final String FORMAT_NAME = "png";
    private static final String FONT_NAME = "Algerian";

    private static final int RAND_COLOR_FC1 = 90;
    private static final int RAND_COLOR_BC1 = 150;
    private static final int RAND_COLOR_FC2 = 200;
    private static final int RAND_COLOR_BC2 = 250;
    private static final int RAND_COLOR_FC3 = 140;
    private static final int RAND_COLOR_BC3 = 180;
    private static final int RAND_COLOR_FC4 = 100;
    private static final int RAND_COLOR_BC4 = 160;
    private static final int TWO = 2;
    private static final int THREE = 3;
    private static final int FOUR = 4;
    private static final int FIVE = 5;
    private static final int SEVEN = 7;
    private static final int EIGHT = 8;
    private static final int TEN = 10;
    private static final int TWENTY = 20;
    private static final int FORTY = 40;
    private static final int LINE_COUNT = 50;
    private static final int LINE_RANDOM = 12;
    private static final int MAX_COLOR = 255;

    private static final double FIVE_PERCENT = 0.05d;
    private static final double PERIOD_COEFFICIENT = 6.2831853071795862d;

    private CaptchaUtil() {
    }

    public static Captcha createCaptcha(int width, int height) {
        return createCaptcha(width, height, FOUR);
    }

    public static Captcha createCaptcha(int width, int height, int captchaSize) {
        int codesLen = CAPTCHA_CODES.length;
        StringBuilder captchaStr = new StringBuilder(captchaSize);
        for (int i = 0; i < captchaSize; i++) {
            captchaStr.append(CAPTCHA_CODES[getRandomNumber(codesLen - 1)]);
        }
        String captchaCode = captchaStr.toString();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(getRandColor(RAND_COLOR_FC1, RAND_COLOR_BC1));
        g2.fillRect(0, 0, width, height);

        Color c = getRandColor(RAND_COLOR_FC2, RAND_COLOR_BC2);
        g2.setColor(c);
        g2.fillRect(0, TWO, width, height - FIVE);

        g2.setColor(getRandColor(RAND_COLOR_FC3, RAND_COLOR_BC3));
        for (int i = 0; i < LINE_COUNT; i++) {
            int x = getRandomNumber(width - 1);
            int y = getRandomNumber(height - 1);
            int xl = getRandomNumber(LINE_RANDOM) + 1;
            int yl = getRandomNumber(LINE_RANDOM) + 1;
            g2.drawLine(x, y, x + xl + FORTY, y + yl + TWENTY);
        }

        int area = (int) (FIVE_PERCENT * width * height);
        for (int i = 0; i < area; i++) {
            int x = getRandomNumber(width);
            int y = getRandomNumber(height);
            int rgb = getRandomIntColor();
            image.setRGB(x, y, rgb);
        }

        shear(g2, width, height, c);

        g2.setColor(getRandColor(RAND_COLOR_FC4, RAND_COLOR_BC4));
        int fontSize = height - FOUR;
        g2.setFont(new Font(FONT_NAME, Font.ITALIC, fontSize));
        char[] chars = captchaCode.toCharArray();
        for (int i = 0; i < captchaSize; i++) {
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / FOUR * RANDOM.nextDouble() * (RANDOM.nextBoolean() ? 1 : -1),
                    Double.parseDouble(Integer.toString((width / captchaSize) * i + fontSize / TWO)),
                    Double.parseDouble(Integer.toString(height / TWO)));
            g2.setTransform(affine);
            g2.drawChars(chars, i, 1, ((width - TEN) / captchaSize) * i + FIVE, height / TWO + fontSize / TWO - TEN);
        }
        g2.dispose();
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            ImageIO.write(image, FORMAT_NAME, bos);
            return new Captcha(captchaCode, Base64.getEncoder().encodeToString(bos.toByteArray()));
        } catch (IOException e) {
            log.error("generate captcha error", e);
        }
        return null;
    }

    private static int getRandomNumber(int number) {
        return RANDOM.nextInt(number);
    }

    private static Color getRandColor(int fc, int bc) {
        fc = Math.min(fc, MAX_COLOR);
        bc = Math.min(bc, MAX_COLOR);
        int r = fc + getRandomNumber(bc - fc);
        int g = fc + getRandomNumber(bc - fc);
        int b = fc + getRandomNumber(bc - fc);
        return new Color(r, g, b);
    }

    private static int getRandomIntColor() {
        int color = 0;
        for (int i = 0; i < THREE; i++) {
            color = color << EIGHT;
            color = color | getRandomNumber(MAX_COLOR);
        }
        return color;
    }

    private static void shear(Graphics g, int width, int height, Color color) {
        shearX(g, width, height, color);
        shearY(g, width, height, color);
    }

    private static void shearX(Graphics g, int width, int height, Color color) {
        int period = getRandomNumber(TWO);
        int frames = 1;
        int phase = getRandomNumber(TWO);
        for (int i = 0; i < height; i++) {
            double d = (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (PERIOD_COEFFICIENT * phase)
                    / frames);
            g.copyArea(0, i, width, 1, (int) d, 0);
            g.setColor(color);
            g.drawLine((int) d, i, 0, i);
            g.drawLine((int) d + width, i, width, i);
        }

    }

    private static void shearY(Graphics g, int width, int height, Color color) {
        int period = getRandomNumber(FORTY) + TEN;
        for (int i = 0; i < width; i++) {
            double d = (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (PERIOD_COEFFICIENT * SEVEN)
                    / TWENTY);
            g.copyArea(i, 0, 1, height, 0, (int) d);
            g.setColor(color);
            g.drawLine(i, (int) d, i, 0);
            g.drawLine(i, (int) d + height, i, height);
        }

    }

    /**
     * 返回验证码类
     */
    public static final class Captcha {

        /**
         * 验证码
         */
        @Getter
        private final String captchaCode;

        /**
         * 验证码图片(base64)
         */
        @Getter
        private final String captchaImage;

        public Captcha(String captchaCode, String captchaImage) {
            this.captchaCode = captchaCode;
            this.captchaImage = captchaImage;
        }
    }
}
